﻿using HelenServer.Common;
using HelenServer.FileStorage.Contracts;

namespace HelenServer.FileStorage.Dal;

[ServiceProvider(typeof(IDalFileStorageService), SqlServerConstants.ProviderName)]
public class DalFileStorageService : IDalFileStorageService
{
    private readonly IRepo<AttachmentDbContext> _repo;
    private readonly IServiceProvider _provider;
    private readonly IXXH64 _xxh;
    private readonly FastDFSContainerOptions _options;
    private readonly ILogger<DalFileStorageService> _logger;

    public DalFileStorageService(
        IReadWriteSplittingDbContextFactory<AttachmentDbContext> factory,
        IServiceProvider provider,
        IXXH64 xxh,
        IOptions<FastDFSContainerOptions> options,
        ILogger<DalFileStorageService> logger)
    {
        _repo = new Repo<AttachmentDbContext>(factory.Create(ReadWritePolicy.ReadWrite));
        _provider = provider;
        _xxh = xxh;
        _options = options.Value;
        _logger = logger;
    }

    public async ValueTask<OperationResult<DownloadModel>> DownloadFileAsync(Operation<int> operation)
    {
        var fs = this.GetProvider();

        var entity = await _repo.FindAsync<Attachment>(n => n.Id == operation.Parameter);

        if (entity is not null)
        {
            var buffer = await fs.DownloadFileAsync(new FileInfoModel { FileId = entity.FileId, GroupName = entity.GroupName, ClusterName = _options.ClusterName });

            return OperationResult<DownloadModel>.OK(new DownloadModel { Id = entity.Id, FileId = entity.FileId, Name = entity.Name, Buffer = buffer });
        }

        return OperationResult<DownloadModel>.Failed("未找到文件存储元信息");
    }

    public async ValueTask<OperationResult<FastDFSFileInfoModel>> GetFileInfo(Operation<int> operation)
    {
        var fs = this.GetProvider();

        var id = operation.Parameter;

        var entity = await _repo.FindAsync<Attachment>(n => n.Id == id);

        return entity is not null
            ? OperationResult<FastDFSFileInfoModel>.OK(await fs.GetFileInfo(new FileInfoModel
            {
                FileId = entity.FileId,
                GroupName = entity.GroupName,
                ClusterName = _options.ClusterName
            }))
            : OperationResult<FastDFSFileInfoModel>.Failed("未找到文件存储元信息");
    }

    public async ValueTask<OperationResult<StorageNodeModel>> GetStorageNodeAsync(Operation<FileGroupModel> operation)
    {
        var fs = this.GetProvider();

        return OperationResult<StorageNodeModel>.OK(await fs.GetStorageNodeAsync(operation.Parameter));
    }

    public async ValueTask<OperationResult<string>> GetTokenAsync(Operation<int> operation, DateTime? dateTime = null)
    {
        var fs = this.GetProvider();

        var entity = await _repo.FindAsync<Attachment>(n => n.Id == operation.Parameter);

        return entity is not null
            ? OperationResult<string>.OK(fs.GetToken(new FilePlaceModel { FileId = entity.FileId, ClusterName = _options.ClusterName }, dateTime))
            : OperationResult<string>.Failed("未找到文件存储元信息");
    }

    public async ValueTask<IReadOnlyCollection<GroupInfoModel>> ListAllGroupInfosAsync(Operation<string> operation)
    {
        var fs = this.GetProvider();

        return await fs.ListAllGroupInfosAsync(operation.Parameter);
    }

    public async ValueTask<OperationResult<GroupInfoModel>> ListOneGroupInfoAsync(Operation<FileGroupModel> operation)
    {
        var fs = this.GetProvider();

        return OperationResult<GroupInfoModel>.OK(await fs.ListOneGroupInfoAsync(operation.Parameter));
    }

    public async ValueTask<IReadOnlyCollection<StorageInfoModel>> ListStorageInfosAsync(Operation<FileGroupModel> operation)
    {
        var fs = this.GetProvider();

        return await fs.ListStorageInfosAsync(operation.Parameter);
    }

    public async ValueTask<OperationResult<StorageNodeModel>> QueryStorageNodeForFileAsync(Operation<int> operation)
    {
        var fs = this.GetProvider();

        var entity = await _repo.FindAsync<Attachment>(n => n.Id == operation.Parameter);

        return entity is not null
            ? OperationResult<StorageNodeModel>.OK(await fs.QueryStorageNodeForFileAsync(new FileInfoModel { FileId = entity.FileId, GroupName = entity.GroupName, ClusterName = _options.ClusterName }))
            : OperationResult<StorageNodeModel>.Failed("未找到文件存储元信息");
    }

    public async ValueTask<IReadOnlyCollection<StorageNodeModel>> QueryStorageNodesForFileAsync(Operation<int> operation)
    {
        var fs = this.GetProvider();

        var entity = await _repo.FindAsync<Attachment>(n => n.Id == operation.Parameter);

        return entity is null
            ? throw new ArgumentException("未找到该Id对应的元数据", nameof(operation))
            : await fs.QueryStorageNodesForFileAsync(new FileInfoModel { FileId = entity.FileId, GroupName = entity.GroupName, ClusterName = _options.ClusterName });
    }

    public async ValueTask<OperationResult<bool>> RemoveFileAsync(Operation<int> operation)
    {
        var fs = this.GetProvider();

        var entity = await _repo.FindAsync<Attachment>(n => n.Id == operation.Parameter);

        if (entity is not null)
        {
            await _repo.RemoveAsync(entity);

            var fileInfo = new FileInfoModel { FileId = entity.FileId, GroupName = entity.GroupName, ClusterName = _options.ClusterName };

            _ = await fs.RemoveFileAsync(fileInfo);

            return OperationResult<bool>.OK(true);
        }

        return OperationResult<bool>.Failed("文件不存在");
    }

    public async ValueTask<OperationResult<int>> UploadFileAsync(Operation<UploadModel> operation, CancellationToken cancellationToken = default)
    {
        var fs = this.GetProvider();

        var model = operation.Parameter;

        var fileId = string.Empty;
        await using var transaction = await _repo.Context.Database.BeginTransactionAsync(cancellationToken);
        try
        {
            _xxh.Update(model.Buffer, 0, model.Buffer.Length);

            var hash = Convert.ToBase64String(_xxh.DigestBytes());

            var exists = _repo.Context.Attachments.Where(n => n.Hash == hash).Select(n => n.Id).FirstOrDefault();
            if (exists != 0)
            {
                _logger.LogInformation("-- 秒传：{name}，Hash：{hash}", model.Name, hash);

                return OperationResult<int>.OK(exists);
            }

            fileId = await fs.UploadFileAsync(operation.Parameter);

            var groupModel = new FileGroupModel { GroupName = _options.GroupName, ClusterName = _options.ClusterName };

            var groupInfo = await fs.ListOneGroupInfoAsync(groupModel);
            var storageNode = await fs.GetStorageNodeAsync(groupModel);
            storageNode.ConnectionAddress.IPAddress = _options.IPAddress;

            var attachment = new Attachment
            {
                FileId = fileId,
                Url = $"http://{storageNode.ConnectionAddress.IPAddress}:{groupInfo.StorageHttpPort}/{_options.GroupName}/{fileId}",
                GroupName = _options.GroupName,
                Name = model.Name,
                Extension = model.Extension,
                Size = model.Buffer.Length,
                AddedBy = model.AddedBy,
                Hash = hash
            };

            await _repo.AddAsync(attachment, cancellationToken);

            await transaction.CommitAsync(cancellationToken);

            return OperationResult<int>.OK(attachment.Id);
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);
            await fs.RemoveFileAsync(new FileInfoModel { FileId = fileId, GroupName = _options.GroupName, ClusterName = _options.ClusterName });

            return OperationResult<int>.Error(ex);
        }
        finally
        {
            _xxh.Reset();
        }
    }

    public async ValueTask<OperationResult<IReadOnlyCollection<int>>> UploadFileRangeAsync(Operation<IEnumerable<UploadModel>> operation, CancellationToken cancellationToken = default)
    {
        var fs = this.GetProvider();

        var models = operation.Parameter;

        var fileId = string.Empty;
        await using var transaction = await _repo.Context.Database.BeginTransactionAsync(cancellationToken);

        var entities = new List<Attachment>();
        var existsEntities = new List<Attachment>();

        try
        {
            foreach (var model in models)
            {
                _xxh.Update(model.Buffer, 0, model.Buffer.Length);
                var hash = Convert.ToBase64String(_xxh.DigestBytes());
                _xxh.Reset();

                var exists = _repo.Context.Attachments.Where(n => n.Hash == hash).Select(n => n.Id).FirstOrDefault();
                if (exists != 0)
                {
                    existsEntities.Add(new Attachment { Id = exists });

                    continue;
                }

                fileId = await fs.UploadFileAsync(model);

                var groupModel = new FileGroupModel { GroupName = _options.GroupName, ClusterName = _options.ClusterName };
                var groupInfo = await fs.ListOneGroupInfoAsync(groupModel);
                var storageNode = await fs.GetStorageNodeAsync(groupModel);
                storageNode.ConnectionAddress.IPAddress = _options.IPAddress;

                var attachment = new Attachment
                {
                    FileId = fileId,
                    Url = $"http://{storageNode.ConnectionAddress.IPAddress}:{groupInfo.StorageHttpPort}/{_options.GroupName}/{fileId}",
                    GroupName = _options.GroupName,
                    Name = model.Name,
                    Extension = model.Extension,
                    Size = model.Buffer.Length,
                    AddedBy = model.AddedBy,
                    Hash = hash
                };

                entities.Add(attachment);
            }

            await _repo.AddRangeAsync(entities, cancellationToken);

            await transaction.CommitAsync(cancellationToken);

            _logger.LogInformation("-- 秒传{count1}个， 上传{count2}个", existsEntities.Count, entities.Count);

            entities.AddRange(existsEntities);

            return OperationResult<IReadOnlyCollection<int>>.OK(entities.Select(n => n.Id).ToList().AsReadOnly());

        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);

            foreach (var item in entities)
            {
                await fs.RemoveFileAsync(new FileInfoModel { FileId = item.FileId, GroupName = _options.GroupName, ClusterName = _options.ClusterName });
            }

            return OperationResult<IReadOnlyCollection<int>>.Error(ex);
        }
    }

    public async ValueTask<OperationResult<IReadOnlyDictionary<int, int[]>>> UploadFileRangeForMigrateAsync(Operation<IEnumerable<KeyValuePair<int, IEnumerable<UploadModel>>>> operation, CancellationToken cancellationToken = default)
    {
        var fs = this.GetProvider();

        var models = operation.Parameter;

        var totalCount = models.Count();

        var fileId = string.Empty;
        await using var transaction = await _repo.Context.Database.BeginTransactionAsync(cancellationToken);

        var box = new Dictionary<int, List<Attachment>>();

        try
        {
            Console.WriteLine("处理UploadModel中");
            foreach (var item in models)
            {
                box.Add(item.Key, new List<Attachment>());

                var newEntities = new List<Attachment>();
                var existsEntities = new List<Attachment>();

                foreach (var model in item.Value)
                {
                    _xxh.Update(model.Buffer, 0, model.Buffer.Length);
                    var hash = Convert.ToBase64String(_xxh.DigestBytes());
                    _xxh.Reset();

                    var exists = _repo.Context.Attachments.Where(n => n.Hash == hash).Select(n => n.Id).FirstOrDefault();
                    if (exists != 0)
                    {
                        existsEntities.Add(new Attachment { Id = exists });

                        continue;
                    }

                    fileId = await fs.UploadFileAsync(model);

                    var groupModel = new FileGroupModel { GroupName = _options.GroupName, ClusterName = _options.ClusterName };
                    var groupInfo = await fs.ListOneGroupInfoAsync(groupModel);
                    var storageNode = await fs.GetStorageNodeAsync(groupModel);
                    storageNode.ConnectionAddress.IPAddress = _options.IPAddress;

                    var attachment = new Attachment
                    {
                        FileId = fileId,
                        Url = $"http://{storageNode.ConnectionAddress.IPAddress}:{groupInfo.StorageHttpPort}/{_options.GroupName}/{fileId}",
                        GroupName = _options.GroupName,
                        Name = model.Name,
                        Extension = model.Extension,
                        Size = model.Buffer.Length,
                        AddedBy = model.AddedBy,
                        Hash = hash
                    };

                    newEntities.Add(attachment);
                }

                newEntities.AddRange(existsEntities);

                box[item.Key].AddRange(newEntities);
            }

            var entities = box.SelectMany(n => n.Value);

            await _repo.AddRangeAsync(entities.Where(n => n.Id == 0), cancellationToken);

            await transaction.CommitAsync(cancellationToken);

            Console.WriteLine("事务已提交");

            return OperationResult<IReadOnlyDictionary<int, int[]>>.OK(
                box.Select(n => new KeyValuePair<int, int[]>(n.Key, n.Value.Select(n => n.Id).ToArray())).ToDictionary(k => k.Key, v => v.Value));
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);

            foreach (var item in box)
            {
                foreach (var entity in item.Value)
                {
                    await fs.RemoveFileAsync(new FileInfoModel
                    {
                        FileId = entity.FileId,
                        GroupName = _options.GroupName,
                        ClusterName = _options.ClusterName
                    });
                }
            }

            return OperationResult<IReadOnlyDictionary<int, int[]>>.Error(ex);
        }
    }

    private IFileStorageProvider GetProvider()
    {
        return _provider.GetRequiredService<IFileStorageProvider>();
    }
}